//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

namespace Microsoft.Research.ScientificWorkflow.UIDesigner.Monitor
{
    using System;
    using System.Linq;
    using Microsoft.Research.eResearch.Blackboard.Common;
    using Microsoft.Research.eResearch.Common.Threading;
    using Microsoft.Research.eResearch.Subscriber;
    using Microsoft.Research.eResearch.Subscriber.Interfaces;
    using Microsoft.Practices.Unity;
    using Microsoft.Research.ScientificWorkflow.TridentUtilities;

    /// <summary>
    /// This class contain logic to integrate with blackboard host
    /// </summary>
    public class MonitorService : IMonitorService
    {
        /// <summary>
        /// currently executing job id.  This is used to filter the blackboard messages
        /// </summary>
        private string jobId;

        /// <summary>
        /// instance of monitor settings
        /// </summary>
        private MonitorSettingService monitorSettingService;

        /// <summary>
        /// Field indicating whether the monitor service has been started.
        /// </summary>
        private bool started;

        /// <summary>
        /// Instance of the blackboard subscriber
        /// </summary>
        private BlackboardSubscriber blackboardSubscriber;

        /// <summary>
        /// Initializes a new instance of the MonitorService class
        /// </summary>
        public MonitorService(IUnityContainer container)
        {
            this.monitorSettingService = container.Resolve<MonitorSettingService>();
            this.blackboardSubscriber = new BlackboardSubscriber();
            this.blackboardSubscriber.BlackboardMessageReceived += this.OnBlackboardMessageReceived;
            this.Start(Guid.Empty);
        }

        /// <summary>
        /// Raised whenever workflow instance created message is recieved from blackboard
        /// </summary>
        public event EventHandler<BlackboardMessageReceivedEventArgs> WorkflowInstanceCreated;

        /// <summary>
        /// Raised whenever workflow instance completed message is recieved from blackboard
        /// </summary>
        public event EventHandler<BlackboardMessageReceivedEventArgs> WorkflowInstanceCompleted;

        /// <summary>
        /// Raised wheneveer workflow, user or activity event message is recieved from blackboard
        /// </summary>
        public event EventHandler<BlackboardMessageReceivedEventArgs> ProcessingStatus;

        /// <summary>
        /// Raised whenever activity event message is recieved from blackboard
        /// </summary>
        public event EventHandler<BlackboardMessageReceivedEventArgs> ActivityMessage;

        /// <summary>
        /// Raised whenever workflow event message is recieved from blackboard
        /// </summary>
        public event EventHandler<BlackboardMessageReceivedEventArgs> WorkflowMessage;

        /// <summary>
        /// Raised whenever activity parameter value message is recieved from blackboard
        /// </summary>
        public event EventHandler<BlackboardMessageReceivedEventArgs> InputOutput;

        /// <summary>
        /// Raised whenever a iteration event message is recieved from blackboard.
        /// </summary>
        public event EventHandler<BlackboardMessageReceivedEventArgs> IterationEvent;

        /// <summary>
        /// Raised whenever a dataproduct event message is recieved from blackboard.
        /// </summary>
        public event EventHandler<BlackboardMessageReceivedEventArgs> DataProductEvent;

        /// <summary>
        /// Raised whenever resource utilization message is recieved from blackboard
        /// </summary>
        public event EventHandler<BlackboardMessageReceivedEventArgs> ResourceUsage;

        /// <summary>
        /// Raised whenever message from blackboard is recieved
        /// </summary>
        public event EventHandler<BlackboardMessageReceivedEventArgs> BlackboardMessageReceived;

        /// <summary>
        /// Raised whenever OutputDataProductReceived message is recieved from blackboard.
        /// </summary>
        public event EventHandler<BlackboardMessageReceivedEventArgs> OutputDataProductReceived;

        /// <summary>
        /// Raised whenever datamodel needs to be cleared
        /// </summary>
        public event EventHandler Clear;

        /// <summary>
        /// Start the subscriber if not already started
        /// </summary>
        /// <param name="jobId">Job id</param>
        public void Start(Guid jobId)
        {
            try
            {
                this.jobId = jobId.ToString();
                if (!this.started)
                {
                    this.blackboardSubscriber.Start();

                    SubscriptionProfile profile = new SubscriptionProfile();
                    profile.Add(new SubscriptionItem("trident:InstanceId"));
                    profile.Add(new SubscriptionItem("trident:WorkflowType"));
                    profile.Add(new SubscriptionItem("trident:ActivityState"));
                    profile.Add(new SubscriptionItem("trident:ActivityName"));
                    profile.Add(new SubscriptionItem("trident:ActivityLabel"));
                    profile.Add(new SubscriptionItem("trident:WorkflowState"));
                    profile.Add(new SubscriptionItem("trident:EventOrder"));
                    profile.Add(new SubscriptionItem("trident:CLRType"));
                    profile.Add(new SubscriptionItem("trident:ActivityPropertyValue"));
                    profile.Add(new SubscriptionItem("trident:FieldName"));
                    profile.Add(new SubscriptionItem("trident:ActivityPropertyDirection"));
                    profile.Add(new SubscriptionItem("trident:CpuResource"));
                    profile.Add(new SubscriptionItem("trident:MemoryResource"));
                    profile.Add(new SubscriptionItem("trident:EventTimestamp"));
                    profile.Add(new SubscriptionItem("time:DateTimeDescription"));
                    profile.Add(new SubscriptionItem("trident:JobId"));
                    profile.Add(new SubscriptionItem("trident:WorkflowName"));
                    profile.Add(new SubscriptionItem("trident:ExceptionDetails"));
                    profile.Add(new SubscriptionItem("trident:UserData"));
                    profile.Add(new SubscriptionItem("trident:UserDataKey"));
                    profile.Add(new SubscriptionItem("trident:IterationValue"));
                    profile.Add(new SubscriptionItem("trident:DataProductID"));
                    profile.Add(new SubscriptionItem("trident:OutputDataProductID"));
                    this.blackboardSubscriber.Subscribe(profile);

                    started = true;
                }
            }
            catch (Exception e)
            {
                TridentErrorHandler.HandleAndThrowBusinessLogicException(e, 
                    TridentErrorConstants.ErrorNumber90000037,
                    TridentResourceManager.GetString("Blackboardservicenotavailable"));
            }
        }

        /// <summary>
        /// Stop the subscriber if already started
        /// </summary>
        public void HaltService()
        {
            try
            {
                if (this.started)
                {
                    this.blackboardSubscriber.Stop();
                    this.started = false;
                }
            }
            catch (Exception)
            {
                throw;
            }
        }

        /// <summary>
        /// Raises ClearEvent
        /// </summary>
        public void ClearModel()
        {
            if (Clear != null)
            {
                this.Clear(this, new EventArgs());
            }
        }

        /// <summary>
        /// Gets a value indicating whether WorkflowInstanceCreated event has binding
        /// </summary>
        private bool WorkflowInstanceCreatedHasBinding
        {
            get { return this.WorkflowInstanceCreated != null; }
        }

        /// <summary>
        /// Gets a value indicating whether WorkflowInstanceCompleted event has binding
        /// </summary>
        private bool WorkflowInstanceCompletedHasBinding
        {
            get { return this.WorkflowInstanceCompleted != null; }
        }

        /// <summary>
        /// Gets a value indicating whether [output data product received has binding].
        /// </summary>
        /// <value>
        /// 	<c>true</c> if [output data product received has binding]; otherwise, <c>false</c>.
        /// </value>
        private bool OutputDataProductReceivedHasBinding
        {
            get { return this.OutputDataProductReceived != null; }
        }

        /// <summary>
        /// Gets a value indicating whether ProcessingStatus event has binding
        /// </summary>
        private bool ProcessingStatusHasBinding
        {
            get { return this.ProcessingStatus != null; }
        }

        /// <summary>
        /// Gets a value indicating whether ActivityMessage event has binding
        /// </summary>
        private bool ActivityMessageHasBinding
        {
            get { return this.ActivityMessage != null; }
        }

        /// <summary>
        /// Gets a value indicating whether WorkflowMessage event has binding
        /// </summary>
        private bool WorkflowMessageHasBinding
        {
            get { return this.WorkflowMessage != null; }
        }

        /// <summary>
        /// Gets a value indicating whether InputOutput event has binding
        /// </summary>
        private bool InputOutputHasBinding
        {
            get { return this.InputOutput != null; }
        }

        /// <summary>
        /// Gets a value indicating whether ResourceUsage event has binding
        /// </summary>
        private bool ResourceUsageHasBinding
        {
            get { return this.ResourceUsage != null; }
        }

        /// <summary>
        /// Gets a value indicating whether user event has binding
        /// </summary>
        private bool IterationEventHasBinding
        {
            get { return this.IterationEvent != null; }
        }

        /// <summary>
        /// Gets a value indicating whether Dataproduct event has binding
        /// </summary>
        private bool DataproductEventHasBinding
        {
            get { return this.DataProductEvent != null; }
        }

        /// <summary>
        /// Gets a value indicating whether BlackboardMessageReceived event has binding
        /// </summary>
        private bool BlackboardMessageReceivedHasBinding
        {
            get { return this.BlackboardMessageReceived != null; }
        }

        /// <summary>
        /// Message parser for checking workflow created event
        /// </summary>
        /// <param name="message">instance of blackboard message</param>
        /// <returns>true if the message contains workflow created event, false otherwise</returns>
        private static bool IsWorkflowInstanceCreated(BlackboardMessage message)
        {
            return message.ContainsKey("trident:WorkflowState") && message["trident:WorkflowState"] == "Created";
        }

        /// <summary>
        /// Message parser for checking workflow completed event
        /// </summary>
        /// <param name="message">instance of blackboard message</param>
        /// <returns>true if the message contains workflow completed event, false otherwise</returns>
        private static bool IsWorkflowInstanceCompleted(BlackboardMessage message)
        {
            return message.ContainsKey("trident:WorkflowState") && message["trident:WorkflowState"] == "Completed";
        }

        /// <summary>
        /// Message parser for checking workflow terminiated event
        /// </summary>
        /// <param name="message">instance of blackboard message</param>
        /// <returns>true if the message contains workflow terminated event, false otherwise</returns>
        private static bool IsWorkflowInstanceTerminated(BlackboardMessage message)
        {
            return message.ContainsKey("trident:WorkflowState") && message["trident:WorkflowState"] == "Terminated";
        }

        /// <summary>
        /// Message parser for checking activity property value event
        /// </summary>
        /// <param name="message">instance of blackboard message</param>
        /// <returns>true if the message contains activity property and value, false otherwise</returns>
        private static bool IsInputOutputEvent(BlackboardMessage message)
        {
            return message.ContainsKey("trident:FieldName") && 
                (message["trident:ActivityPropertyDirection"] == "Input" || 
                message["trident:ActivityPropertyDirection"] == "Output" ||
                message["trident:ActivityPropertyDirection"] == "InOut");
        }

        /// <summary>
        /// Message parser for checking resource event
        /// </summary>
        /// <param name="message">instance of blackboard message</param>
        /// <returns>true if the message contains activity property and value, false otherwise</returns>
        private static bool IsResourceEvent(BlackboardMessage message)
        {
            return message.Keys.FirstOrDefault(key => (key == "trident:CpuResource" || key == "trident:MemoryResource")) != null;
        }

        /// <summary>
        /// Message parser for checking workflow event
        /// </summary>
        /// <param name="message">instance of blackboard message</param>
        /// <returns>true if the message contains activity property and value, false otherwise</returns>
        private static bool IsWorkflowEvent(BlackboardMessage message)
        {
            return message.ContainsKey("trident:WorkflowState");
        }

        /// <summary>
        /// Message parser for checking activity event
        /// </summary>
        /// <param name="message">instance of blackboard message</param>
        /// <returns>true if the message contains activity property and value, false otherwise</returns>
        private static bool IsActivityEvent(BlackboardMessage message)
        {
            return message.ContainsKey("trident:ActivityState") && !message.ContainsKey("trident:FieldName");
        }

        /// <summary>
        /// Message parser for checking user event
        /// </summary>
        /// <param name="message">instance of blackboard message</param>
        /// <returns>true if the message contains user data, false otherwise</returns>
        private static bool IsIterationEvent(BlackboardMessage message)
        {
            return message.ContainsKey("trident:IterationValue");
        }

        /// <summary>
        /// Message parser for checking dataproduct event
        /// </summary>
        /// <param name="message">instance of blackboard message</param>
        /// <returns>true if the message contains user data, false otherwise</returns>
        private static bool IsDataproductEvent(BlackboardMessage message)
        {
            return message.ContainsKey("trident:DataProductID");
        }

        /// <summary>
        /// Determines whether [is output data product event] [the specified message].
        /// </summary>
        /// <param name="message">The message.</param>
        /// <returns>
        /// 	<c>true</c> if [is output data product event] [the specified message]; otherwise, <c>false</c>.
        /// </returns>
        private static bool IsOutputDataProductEvent(BlackboardMessage message)
        {
            return message.ContainsKey("trident:OutputDataProductID");
        }

        /// <summary>
        /// Event handler for black board message recieved event
        /// </summary>
        /// <param name="sender">instance of the subscriber</param>
        /// <param name="e">instance containing the blackboard message</param>
        private void OnBlackboardMessageReceived(object sender, BlackboardMessageReceivedEventArgs e)
        {
            if (e.IncomingMessage["trident:JobId"] != this.jobId)
            {
                return;
            }
            if (this.monitorSettingService.Settings.EnableMonitoring)
            {
                if (this.WorkflowInstanceCreatedHasBinding && IsWorkflowInstanceCreated(e.IncomingMessage))
                {
                    this.WorkflowInstanceCreated(this, e);
                }

                if (this.ProcessingStatusHasBinding && (IsActivityEvent(e.IncomingMessage) || IsWorkflowEvent(e.IncomingMessage)))
                {
                    this.ProcessingStatus(this, e);
                }

                if (this.monitorSettingService.Settings.NotifyWorkflowEvents)
                {
                    if (this.WorkflowMessageHasBinding && IsWorkflowEvent(e.IncomingMessage))
                    {
                        this.WorkflowMessage(this, e);
                    }

                    // Stop the blackboard subscription if the workflow is terminated.
                    if (IsWorkflowInstanceTerminated(e.IncomingMessage))
                    {
                        this.HaltService();
                    }
                }

                if (this.monitorSettingService.Settings.NotifyActivityEvents)
                {
                    if (this.ActivityMessageHasBinding && IsActivityEvent(e.IncomingMessage))
                    {
                        this.ActivityMessage(this, e);
                    }
                    if (this.IterationEventHasBinding && IsIterationEvent(e.IncomingMessage))
                    {
                        this.IterationEvent(this, e);
                    }
                }

                if (this.monitorSettingService.Settings.NotifyTridentEvents)
                {
                    if (this.ResourceUsageHasBinding && IsResourceEvent(e.IncomingMessage))
                    {
                        this.ResourceUsage(this, e);
                    }

                    if (this.InputOutputHasBinding && IsInputOutputEvent(e.IncomingMessage))
                    {
                        this.InputOutput(this, e);
                    }
                }

                if (this.monitorSettingService.Settings.EnableLogging && this.monitorSettingService.Settings.LevelAll)
                {
                    if (this.BlackboardMessageReceivedHasBinding)
                    {
                        this.BlackboardMessageReceived(this, e);
                    }
                }

                if (this.WorkflowInstanceCompletedHasBinding && IsWorkflowInstanceCompleted(e.IncomingMessage))
                {
                    this.WorkflowInstanceCompleted(this, e);
                }

                if (this.DataproductEventHasBinding && IsDataproductEvent(e.IncomingMessage))
                {
                    this.DataProductEvent(this, e);
                }

                if (this.OutputDataProductReceivedHasBinding && IsOutputDataProductEvent(e.IncomingMessage))
                {
                    this.OutputDataProductReceived(this, e);
                }
            }
        }
    }
}
